/*
 * Copyright (c) 2023 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @brief UART driver for Intel FPGA UART Core IP
 * Reference : Embedded Peripherals IP User Guide (22.3 and above): 55.
 * Lightweight UART Core
 */

#define DT_DRV_COMPAT   intel_lw_uart
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>

#include <zephyr/drivers/serial/uart_intel_lw.h>

/* register offsets */
#define INTEL_LW_UART_OFFSET  (0x4)

#define INTEL_LW_UART_RXDATA_REG_OFFSET    (0 * INTEL_LW_UART_OFFSET)
#define INTEL_LW_UART_TXDATA_REG_OFFSET    (1 * INTEL_LW_UART_OFFSET)
#define INTEL_LW_UART_STATUS_REG_OFFSET    (2 * INTEL_LW_UART_OFFSET)
#define INTEL_LW_UART_CONTROL_REG_OFFSET   (3 * INTEL_LW_UART_OFFSET)
#define INTEL_LW_UART_DIVISOR_REG_OFFSET   (4 * INTEL_LW_UART_OFFSET)
#define INTEL_LW_UART_EOP_REG_OFFSET       (5 * INTEL_LW_UART_OFFSET)

/*status register mask */
#define INTEL_LW_UART_STATUS_PE_MSK        (0x1)
#define INTEL_LW_UART_STATUS_FE_MSK        (0x2)
#define INTEL_LW_UART_STATUS_BRK_MSK       (0x4)
#define INTEL_LW_UART_STATUS_ROE_MSK       (0x8)
#define INTEL_LW_UART_STATUS_TOE_MSK       (0x10)
#define INTEL_LW_UART_STATUS_TMT_MSK       (0x20)
#define INTEL_LW_UART_STATUS_TRDY_MSK      (0x40)
#define INTEL_LW_UART_STATUS_RRDY_MSK      (0x80)
#define INTEL_LW_UART_STATUS_DCTS_MSK      (0x400)
#define INTEL_LW_UART_STATUS_CTS_MSK       (0x800)
#define INTEL_LW_UART_STATUS_E_MSK         (0x100)
#define INTEL_LW_UART_STATUS_EOP_MSK	   (0x1000)

/* control register mask */
#define INTEL_LW_UART_CONTROL_TMT_MSK      (0x20)
#define INTEL_LW_UART_CONTROL_TRDY_MSK     (0x40)
#define INTEL_LW_UART_CONTROL_RRDY_MSK     (0x80)
#define INTEL_LW_UART_CONTROL_E_MSK        (0x100)
#define INTEL_LW_UART_CONTROL_TRBK_MSK     (0x200)
#define INTEL_LW_UART_CONTROL_DCTS_MSK     (0x400)
#define INTEL_LW_UART_CONTROL_RTS_MSK      (0x800)
#define INTEL_LW_UART_CONTROL_EOP_MSK      (0x1000)

/* defined values */
#define UART_INTEL_LW_NO_ERROR (0u)
#define INTEL_LW_UART_CLEAR_STATUS_VAL (0u)
#define INTEL_LW_UART_PENDING_MASK  (INTEL_LW_UART_STATUS_RRDY_MSK | \
			INTEL_LW_UART_STATUS_TRDY_MSK | INTEL_LW_UART_STATUS_E_MSK \
			| INTEL_LW_UART_STATUS_EOP_MSK)

/***********************/
/* configuration flags */
/*
 * The value INTEL_LW_UART_FB is a value set in the devices flag field to
 * indicate that the device has a fixed baud rate; i.e. if this flag is set
 * software can not control the baud rate of the device.
 */
#define INTEL_LW_UART_FB 0x1

/*
 * The value INTEL_LW_UART_FC is a value set in the device flag field to
 * indicate the device is using flow control, i.e. the driver must
 * throttle on transmit if the nCTS pin is low.
 */
#define INTEL_LW_UART_FC 0x2

/* end of configuration flags */
/******************************/

/* device data */
struct uart_intel_lw_device_data {
	struct uart_config uart_cfg; /* stores uart config from device tree*/
	struct k_spinlock lock;
	uint32_t status_act; /* stores value of status register. */
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	uart_irq_callback_user_data_t cb;  /**< Callback function pointer */
	void *cb_data;  /**< Callback function arg */
#ifdef CONFIG_UART_INTEL_LW_EOP
	uint8_t set_eop_cb;
	uart_irq_callback_user_data_t cb_eop;  /**< Callback function pointer */
	void *cb_data_eop;  /**< Callback function arg */
#endif /* CONFIG_UART_INTEL_LW_EOP */
	uint32_t control_val; /* stores value to set control register. */
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
};

/*
 * device config:
 * stores data that cannot be changed during run time.
 */
struct uart_intel_lw_device_config {
	mm_reg_t base;
	uint32_t flags;              /* refer to configuration flags */
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	uart_irq_config_func_t  irq_config_func;
	unsigned int irq_num;
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
};

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
/**
 * function prototypes
 */
static int uart_intel_lw_irq_update(const struct device *dev);
static int uart_intel_lw_irq_tx_ready(const struct device *dev);
static int uart_intel_lw_irq_rx_ready(const struct device *dev);
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

/**
 * @brief Poll the device for input.
 *
 * This is a non-blocking function.
 *
 * This driver supports interrupt driven api.
 * Polling for data under normal operation, might cause unexpected behaviour.
 * If users wish to poll in for data, please ensure that the data is not retrieved
 * in interrupt.
 *
 * If UART_LINE_CTRL is enabled, please do not disable the hardware
 * interrupt for this uart device, as the flow control is handled in uart_intel_lw_dcts_isr.
 *
 * @param dev UART device instance
 * @param p_char Pointer to character
 *
 * @return 0 if a character arrived, -1 if input buffer is empty.
 * -EINVAL if p_char is null pointer
 */
static int uart_intel_lw_poll_in(const struct device *dev, unsigned char *p_char)
{
	const struct uart_intel_lw_device_config *config = dev->config;
	struct uart_intel_lw_device_data *data = dev->data;
	int ret_val = -1;
	uint32_t status;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(p_char != NULL, "p_char is null pointer!");

	/* Stop, if p_char is null pointer */
	if (p_char == NULL) {
		return -EINVAL;
	}

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	/* check if received character is ready.*/
	status = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET);
	if (status & INTEL_LW_UART_STATUS_RRDY_MSK) {
		/* got a character */
		*p_char = sys_read32(config->base + INTEL_LW_UART_RXDATA_REG_OFFSET);
		ret_val = 0;
	}

	k_spin_unlock(&data->lock, key);

	return ret_val;
}

/**
 * @brief Output a character in polled mode.
 *
 * This function will block until transmitter is ready.
 * Then, a character will be transmitted.
 *
 * This driver supports interrupt driven api.
 * Polling for data under normal operation, might cause unexpected behaviour.
 * If users wish to poll out data, please ensure that the data is not transmitted
 * in interrupt.
 *
 * If UART_LINE_CTRL is enabled and users wish to poll out only 1 character,
 * please enable UART_INTEL_LW_AUTO_LINE_CTRL_POLL. Please note that this might
 * be inefficient, in case of polling out several characters. Instead of using
 * UART_INTEL_LW_AUTO_LINE_CTRL_POLL, please consider using the api: uart_drv_cmd
 * with CMD_POLL_ASSERT_RTS before polling out. Then use CMD_POLL_DEASSERT_RTS to
 * resume normal operation after all characters are polled out.
 *
 * Please do not set CMD_TRBK_EN, when polling out data.
 *
 * @param dev UART device instance
 * @param c Character to send
 */
static void uart_intel_lw_poll_out(const struct device *dev, unsigned char c)
{
	const struct uart_intel_lw_device_config *config = dev->config;
	struct uart_intel_lw_device_data *data = dev->data;
	uint32_t status;
	bool tx_is_free = false;
	bool poll_out_done = false;
	k_spinlock_key_t key;

	/* wait until uart is free to transmit.*/
	do {
		key = k_spin_lock(&data->lock);

		if (tx_is_free == false) {
			status = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET);

			/* wait until there is space in fifo. */
			if (status & INTEL_LW_UART_STATUS_TRDY_MSK) {
#ifdef CONFIG_UART_INTEL_LW_AUTO_LINE_CTRL_POLL
				data->control_val |= INTEL_LW_UART_CONTROL_RTS_MSK;
				sys_write32(data->control_val, config->base
					+ INTEL_LW_UART_CONTROL_REG_OFFSET);
#endif
				sys_write32(c, config->base + INTEL_LW_UART_TXDATA_REG_OFFSET);
				tx_is_free = true;
			}
		} else {
			status = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET);

			/* wait until fifo data is shifted out. */
			if (status & INTEL_LW_UART_STATUS_TMT_MSK) {
#ifdef CONFIG_UART_INTEL_LW_AUTO_LINE_CTRL_POLL
				data->control_val &= ~INTEL_LW_UART_CONTROL_RTS_MSK;
				sys_write32(data->control_val, config->base
					+ INTEL_LW_UART_CONTROL_REG_OFFSET);
#endif
				poll_out_done = true;
			}
		}

		k_spin_unlock(&data->lock, key);
	} while (!poll_out_done);
}

/**
 * @brief Initialise an instance of the driver
 *
 * This function initialise the interrupt configuration for the driver.
 *
 * @param dev UART device instance
 *
 * @return 0 to indicate success.
 */
static int uart_intel_lw_init(const struct device *dev)
{
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;
	/* clear status to ensure, that interrupts are not triggered due to old status. */
	sys_write32(INTEL_LW_UART_CLEAR_STATUS_VAL, config->base
				+ INTEL_LW_UART_STATUS_REG_OFFSET);

	/*
	 * Enable hardware interrupt.
	 * The corresponding csr from IP still needs to be set,
	 * so that the IP generates interrupt signal.
	 */
	config->irq_config_func(dev);

#ifdef CONFIG_UART_LINE_CTRL
	/* Enable DCTS interrupt. */
	data->control_val = INTEL_LW_UART_CONTROL_DCTS_MSK;
#endif /* CONFIG_UART_LINE_CTRL */

	sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET);

#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

	return 0;
}

/**
 * @brief Check if an error was received
 * If error is received, it will be mapped to uart_rx_stop_reason.
 * This function should be called after irq_update.
 * If interrupt driven API is not enabled,
 * this function will read and clear the status register.
 *
 * @param dev UART device struct
 *
 * @return UART_ERROR_OVERRUN, UART_ERROR_PARITY, UART_ERROR_FRAMING,
 * UART_BREAK if an error was detected, 0 otherwise.
 */
static int uart_intel_lw_err_check(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	int err = UART_INTEL_LW_NO_ERROR;

#ifndef CONFIG_UART_INTERRUPT_DRIVEN
	const struct uart_intel_lw_device_config *config = dev->config;
	k_spinlock_key_t key = k_spin_lock(&data->lock);

	data->status_act = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET);
#endif

	if (data->status_act & INTEL_LW_UART_STATUS_E_MSK) {
		if (data->status_act & INTEL_LW_UART_STATUS_PE_MSK) {
			err |= UART_ERROR_PARITY;
		}

		if (data->status_act & INTEL_LW_UART_STATUS_FE_MSK) {
			err |= UART_ERROR_FRAMING;
		}

		if (data->status_act & INTEL_LW_UART_STATUS_BRK_MSK) {
			err |= UART_BREAK;
		}

		if (data->status_act & INTEL_LW_UART_STATUS_ROE_MSK) {
			err |= UART_ERROR_OVERRUN;
		}
	}

#ifndef CONFIG_UART_INTERRUPT_DRIVEN
	/* clear status */
	sys_write32(INTEL_LW_UART_CLEAR_STATUS_VAL, config->base
	+ INTEL_LW_UART_STATUS_REG_OFFSET);
	k_spin_unlock(&data->lock, key);
#endif

	return err;
}

#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
/***
 * @brief helper function to check, if the configuration is support.
 * The only parameter that can be changed during runtime is the baudrate.
 *
 * @param  cfg_stored The original configuration.
 * @param  cfg_in     The input configuration.
 * @return true if parameter other than baudrate remains the same. otherwise, false.
 */
static bool uart_intel_lw_check_configuration(const struct uart_config *cfg_stored,
				   const struct uart_config *cfg_in)
{
	bool ret_val = false;

	if ((cfg_stored->parity == cfg_in->parity)
		&& (cfg_stored->stop_bits == cfg_in->stop_bits)
		&& (cfg_stored->data_bits == cfg_in->data_bits)
		&& (cfg_stored->flow_ctrl == cfg_in->flow_ctrl)) {
		ret_val = true;
	}

	return ret_val;
}

/**
 * @brief Set UART configuration using data from *cfg_in.
 *
 * @param dev UART  Device struct
 * @param cfg_in    The input configuration.
 *
 * @return 0 if success, -ENOTSUP, if input from cfg_in is not configurable.
 * -EINVAL if cfg_in is null pointer
 */
static int uart_intel_lw_configure(const struct device *dev,
				   const struct uart_config *cfg_in)
{
	const struct uart_intel_lw_device_config *config = dev->config;
	struct uart_intel_lw_device_data * const data = dev->data;
	struct uart_config * const cfg_stored = &data->uart_cfg;
	uint32_t divisor_val;
	int ret_val;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(cfg_in != NULL, "cfg_in is null pointer!");

	/* Stop, if cfg_in is null pointer */
	if (cfg_in == NULL) {
		return -EINVAL;
	}

	/* check if configuration is supported. */
	if (uart_intel_lw_check_configuration(cfg_stored, cfg_in)
		&& !(config->flags & INTEL_LW_UART_FB)) {
		/* parameter is valid, just return ok if baudrate is the same. */
		if (cfg_stored->baudrate != cfg_in->baudrate) {
			/* calculate and set baudrate. */
			divisor_val = (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC/cfg_in->baudrate) - 1;
			sys_write32(divisor_val, config->base + INTEL_LW_UART_DIVISOR_REG_OFFSET);

			/* update stored data. */
			cfg_stored->baudrate = cfg_in->baudrate;
		}

		ret_val = 0;
	} else {
		/* return not supported */
		ret_val = -ENOTSUP;
	}

	return ret_val;
}
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */

/**
 * @brief Get UART configuration and stores in *cfg_out.
 *
 * @param dev UART  Device struct
 * @param cfg_out   The output configuration.
 *
 * @return 0 if success.
 * -EINVAL if cfg_out is null pointer
 */
static int uart_intel_lw_config_get(const struct device *dev,
				struct uart_config *cfg_out)
{
	const struct uart_intel_lw_device_data *data = dev->data;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(cfg_out != NULL, "cfg_out is null pointer!");

	/* Stop, if cfg_out is null pointer */
	if (cfg_out == NULL) {
		return -EINVAL;
	}

	*cfg_out = data->uart_cfg;
	return 0;
}

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
/**
 * @brief Fill FIFO with data
 * This function is expected to be called from UART interrupt handler (ISR),
 * if uart_irq_tx_ready() returns true. This function does not block!
 *
 * @param dev UART device struct
 * @param tx_data Data to transmit
 * @param size Number of bytes to send
 *
 * @return Number of bytes sent
 */
static int uart_intel_lw_fifo_fill(const struct device *dev,
				  const uint8_t *tx_data,
				  int size)
{
	const struct uart_intel_lw_device_config *config = dev->config;
	struct uart_intel_lw_device_data *data = dev->data;
	int ret_val = 0;
	k_spinlock_key_t key;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(tx_data != NULL, "tx_data is null pointer!");

	/* Stop, if tx_data is null pointer */
	if (tx_data == NULL) {
		return 0;
	}

	/* Stop, if transmit break is set */
	if (data->control_val & INTEL_LW_UART_CONTROL_TRBK_MSK) {
		return 0;
	}

	do {
		if (data->status_act & INTEL_LW_UART_STATUS_TRDY_MSK) {
			key = k_spin_lock(&data->lock);
			sys_write32(tx_data[ret_val++], config->base
				+ INTEL_LW_UART_TXDATA_REG_OFFSET);
			data->status_act = sys_read32(config->base
						+ INTEL_LW_UART_STATUS_REG_OFFSET);
			k_spin_unlock(&data->lock, key);
		} else {
			/* stop because tx fifo is full! */
			break;
		}
	} while ((size - ret_val) > 0);

	return ret_val;
}

/**
 * @brief Read data from FIFO
 * This function is expected to be called from UART interrupt handler (ISR),
 * if uart_irq_rx_ready() returns true.
 *
 * @param dev UART device struct
 * @param rx_data Data container
 * @param size Container size
 *
 * @return Number of bytes read
 */
static int uart_intel_lw_fifo_read(const struct device *dev, uint8_t *rx_data,
				  const int size)
{
	const struct uart_intel_lw_device_config *config = dev->config;
	struct uart_intel_lw_device_data *data = dev->data;
	int ret_val = 0;
	k_spinlock_key_t key;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(rx_data != NULL, "rx_data is null pointer!");

	/* Stop, if rx_data is null pointer */
	if (rx_data == NULL) {
		return 0;
	}

	do {
		if (data->status_act & INTEL_LW_UART_STATUS_RRDY_MSK) {
			key = k_spin_lock(&data->lock);
			rx_data[ret_val++] = sys_read32(config->base +
						INTEL_LW_UART_RXDATA_REG_OFFSET);
			data->status_act = sys_read32(config->base
						      + INTEL_LW_UART_STATUS_REG_OFFSET);

			k_spin_unlock(&data->lock, key);
		} else {
			break;
		}
	} while ((size - ret_val) > 0);

	return ret_val;
}

/**
 * @brief Enable TX interrupt
 *
 * @param dev UART device struct
 */
static void uart_intel_lw_irq_tx_enable(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	data->control_val |= INTEL_LW_UART_CONTROL_TRDY_MSK;

#ifdef CONFIG_UART_LINE_CTRL
	/* also enable RTS, if flow control is enabled. */
	data->control_val |= INTEL_LW_UART_CONTROL_RTS_MSK;
#endif

	sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Disable TX interrupt
 *
 * @param dev UART device struct
 */
static void uart_intel_lw_irq_tx_disable(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	data->control_val &= ~INTEL_LW_UART_CONTROL_TRDY_MSK;

#ifdef CONFIG_UART_LINE_CTRL
	/* also disable RTS, if flow control is enabled. */
	data->control_val &= ~INTEL_LW_UART_CONTROL_RTS_MSK;
#endif

	sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Check if UART TX buffer can accept a new char.
 *
 * @param dev UART device struct
 *
 * @return 1 if TX interrupt is enabled and at least one char can be written to UART.
 *         0 if device is not ready to write a new byte.
 */
static int uart_intel_lw_irq_tx_ready(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	int ret_val = 0;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	/* if TX interrupt is enabled */
	if (data->control_val & INTEL_LW_UART_CONTROL_TRDY_MSK) {
		/* IP core does not have fifo. Wait until tx data is completely shifted. */
		if (data->status_act & INTEL_LW_UART_STATUS_TMT_MSK) {
			ret_val = 1;
		}
	}

#ifdef CONFIG_UART_LINE_CTRL
	/* if flow control is enabled, set tx not ready, if CTS is low. */
	if ((data->status_act & INTEL_LW_UART_STATUS_CTS_MSK) == 0) {
		ret_val = 0;
	}

#endif /* CONFIG_UART_LINE_CTRL */

	k_spin_unlock(&data->lock, key);

	return ret_val;
}

/**
 * @brief Check if nothing remains to be transmitted
 *
 * @param dev UART device struct
 *
 * @return 1 if nothing remains to be transmitted, 0 otherwise
 */
static int uart_intel_lw_irq_tx_complete(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	int ret_val = 0;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	if (data->status_act & INTEL_LW_UART_STATUS_TMT_MSK) {
		ret_val = 1;
	}

	k_spin_unlock(&data->lock, key);

	return ret_val;
}

/**
 * @brief Enable RX interrupt in
 *
 * @param dev UART device struct
 */
static void uart_intel_lw_irq_rx_enable(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	data->control_val |= INTEL_LW_UART_CONTROL_RRDY_MSK;
	sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Disable RX interrupt
 *
 * @param dev UART device struct
 */
static void uart_intel_lw_irq_rx_disable(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	data->control_val &= ~INTEL_LW_UART_CONTROL_RRDY_MSK;
	sys_write32(data->control_val, config->base + INTEL_LW_UART_CONTROL_REG_OFFSET);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Check if Rx IRQ has been raised
 *
 * @param dev UART device struct
 *
 * @return 1 if an IRQ is ready, 0 otherwise
 */
static int uart_intel_lw_irq_rx_ready(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	int ret_val = 0;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	/* if RX interrupt is enabled */
	if (data->control_val & INTEL_LW_UART_CONTROL_RRDY_MSK) {
		/* check for space in rx data register */
		if (data->status_act & INTEL_LW_UART_STATUS_RRDY_MSK) {
			ret_val = 1;
		}
	}

	k_spin_unlock(&data->lock, key);

	return ret_val;
}

/**
 * @brief This function will cache the status register.
 *
 * @param dev UART device struct
 *
 * @return 1 for success.
 */
static int uart_intel_lw_irq_update(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	data->status_act = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET);

	k_spin_unlock(&data->lock, key);

	return 1;
}

/**
 * @brief Check if any IRQ is pending
 *
 * @param dev UART device struct
 *
 * @return 1 if an IRQ is pending, 0 otherwise
 */
static int uart_intel_lw_irq_is_pending(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	int ret_val = 0;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	if (data->status_act & data->control_val & INTEL_LW_UART_PENDING_MASK) {
		ret_val = 1;
	}

	k_spin_unlock(&data->lock, key);

	return ret_val;
}

/**
 * @brief Set the callback function pointer for IRQ.
 *
 * @param dev UART device struct
 * @param cb Callback function pointer.
 * @param cb_data Data to pass to callback function.
 */
static void uart_intel_lw_irq_callback_set(const struct device *dev,
					  uart_irq_callback_user_data_t cb,
					  void *cb_data)
{
	struct uart_intel_lw_device_data *data = dev->data;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

#ifdef CONFIG_UART_INTEL_LW_EOP
	if (data->set_eop_cb) {
		data->cb_eop = cb;
		data->cb_data_eop = cb_data;
		data->set_eop_cb = 0;
	} else {
		data->cb = cb;
		data->cb_data = cb_data;
	}
#else
	data->cb = cb;
	data->cb_data = cb_data;
#endif /* CONFIG_UART_INTEL_LW_EOP */

	k_spin_unlock(&data->lock, key);
}

#ifdef CONFIG_UART_LINE_CTRL
/**
 * @brief DCTS Interrupt service routine.
 *
 * Handles assertion and deassettion of CTS/RTS stignal
 *
 * @param dev Pointer to UART device struct
 */
static void uart_intel_lw_dcts_isr(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	/* Assume that user follows zephyr requirement and update status in their call back. */
	if (data->status_act & INTEL_LW_UART_STATUS_CTS_MSK) {

		/* Assert RTS to inform other UART. */
		data->control_val |= INTEL_LW_UART_CONTROL_RTS_MSK;
		sys_write32(data->control_val, config->base
			    + INTEL_LW_UART_CONTROL_REG_OFFSET);
	} else {
		/* other UART deasserts RTS */
		if (data->status_act & INTEL_LW_UART_STATUS_TMT_MSK) {
			/* only deasserts if not transmitting. */
			data->control_val &= ~INTEL_LW_UART_CONTROL_RTS_MSK;
			sys_write32(data->control_val, config->base
				    + INTEL_LW_UART_CONTROL_REG_OFFSET);
		}
	}

	k_spin_unlock(&data->lock, key);
}
#endif /* CONFIG_UART_LINE_CTRL */

/**
 * @brief Interrupt service routine.
 *
 * This simply calls the callback function, if one exists.
 *
 * @param dev Pointer to UART device struct
 *
 */
static void uart_intel_lw_isr(const struct device *dev)
{
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;

	uart_irq_callback_user_data_t callback = data->cb;

	if (callback) {
		callback(dev, data->cb_data);
	}

	/* Post ISR */
#if CONFIG_UART_INTEL_LW_EOP
	data->status_act = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET);
	if (data->status_act & INTEL_LW_UART_STATUS_EOP_MSK) {
		callback = data->cb_eop;
		if (callback) {
			callback(dev, data->cb_data_eop);
		}
	}
#endif /* CONFIG_UART_INTEL_LW_EOP */

#ifdef CONFIG_UART_LINE_CTRL
	data->status_act = sys_read32(config->base + INTEL_LW_UART_STATUS_REG_OFFSET);
	/* handles RTS/CTS signal */
	if (data->status_act & INTEL_LW_UART_STATUS_DCTS_MSK) {
		uart_intel_lw_dcts_isr(dev);
	}
#endif

	/* clear status after all interrupts are handled. */
	sys_write32(INTEL_LW_UART_CLEAR_STATUS_VAL, config->base
				+ INTEL_LW_UART_STATUS_REG_OFFSET);
}

#ifdef CONFIG_UART_DRV_CMD
/**
 * @brief Send extra command to driver
 *
 * @param dev UART device struct
 * @param cmd Command to driver
 * @param p Parameter to the command
 *
 * @return 0 if successful, failed otherwise
 */
static int uart_intel_lw_drv_cmd(const struct device *dev, uint32_t cmd,
				 uint32_t p)
{
	struct uart_intel_lw_device_data *data = dev->data;
	const struct uart_intel_lw_device_config *config = dev->config;

	int ret_val = -ENOTSUP;
	k_spinlock_key_t key = k_spin_lock(&data->lock);

	switch (cmd) {
#if CONFIG_UART_INTEL_LW_EOP
	case CMD_ENABLE_EOP:
		/* enable EOP interrupt */
		data->control_val |= INTEL_LW_UART_CONTROL_EOP_MSK;
		sys_write32(data->control_val, config->base
					+ INTEL_LW_UART_CONTROL_REG_OFFSET);

		/* set EOP character */
		sys_write32((uint8_t) p, config->base + INTEL_LW_UART_EOP_REG_OFFSET);

		/* after this, user needs to call uart_irq_callback_set
		 * to set data->cb_eop and data->cb_data_eop!
		 */
		data->set_eop_cb = 1;
		ret_val = 0;
		break;

	case CMD_DISABLE_EOP:
		/* disable EOP interrupt */
		data->control_val &= ~INTEL_LW_UART_CONTROL_EOP_MSK;
		sys_write32(data->control_val, config->base
					+ INTEL_LW_UART_CONTROL_REG_OFFSET);

		/* clear call back */
		data->cb_eop = NULL;
		data->cb_data_eop = NULL;
		ret_val = 0;
		break;
#endif /* CONFIG_UART_INTEL_LW_EOP */

	case CMD_TRBK_EN:
		/* enable transmit break */
		data->control_val |= INTEL_LW_UART_CONTROL_TRBK_MSK;
		sys_write32(data->control_val, config->base
			    + INTEL_LW_UART_CONTROL_REG_OFFSET);
		ret_val = 0;
		break;

	case CMD_TRBK_DIS:
		/* disable transmit break */
		data->control_val &= ~INTEL_LW_UART_CONTROL_TRBK_MSK;
		sys_write32(data->control_val, config->base
			    + INTEL_LW_UART_CONTROL_REG_OFFSET);
		ret_val = 0;
		break;

	case CMD_POLL_ASSERT_RTS:
		/* assert RTS */
		data->control_val |= INTEL_LW_UART_CONTROL_RTS_MSK;
		sys_write32(data->control_val, config->base
			    + INTEL_LW_UART_CONTROL_REG_OFFSET);
		ret_val = 0;
		break;

	case CMD_POLL_DEASSERT_RTS:
		/* deassert RTS */
		data->control_val &= ~INTEL_LW_UART_CONTROL_RTS_MSK;
		sys_write32(data->control_val, config->base
			    + INTEL_LW_UART_CONTROL_REG_OFFSET);
		ret_val = 0;
		break;

	default:
		ret_val = -ENOTSUP;
		break;
	};

	k_spin_unlock(&data->lock, key);

	return ret_val;
}

#endif /* CONFIG_UART_DRV_CMD */

#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

static DEVICE_API(uart, uart_intel_lw_driver_api) = {
	.poll_in = uart_intel_lw_poll_in,
	.poll_out = uart_intel_lw_poll_out,
	.err_check = uart_intel_lw_err_check,
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
	.configure = uart_intel_lw_configure,
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */
	.config_get = uart_intel_lw_config_get,

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	.fifo_fill = uart_intel_lw_fifo_fill,
	.fifo_read = uart_intel_lw_fifo_read,
	.irq_tx_enable = uart_intel_lw_irq_tx_enable,
	.irq_tx_disable = uart_intel_lw_irq_tx_disable,
	.irq_tx_ready = uart_intel_lw_irq_tx_ready,
	.irq_tx_complete = uart_intel_lw_irq_tx_complete,
	.irq_rx_enable = uart_intel_lw_irq_rx_enable,
	.irq_rx_disable = uart_intel_lw_irq_rx_disable,
	.irq_rx_ready = uart_intel_lw_irq_rx_ready,
	.irq_is_pending = uart_intel_lw_irq_is_pending,
	.irq_update = uart_intel_lw_irq_update,
	.irq_callback_set = uart_intel_lw_irq_callback_set,
#endif

#ifdef CONFIG_UART_DRV_CMD
	.drv_cmd = uart_intel_lw_drv_cmd,
#endif
};

#ifdef CONFIG_UART_INTERRUPT_DRIVEN

#define UART_INTEL_LW_IRQ_CONFIG_FUNC(n)                                        \
	static void uart_intel_lw_irq_config_func_##n(const struct device *dev) \
	{                                                                       \
		IRQ_CONNECT(DT_INST_IRQN(n),                                    \
				DT_INST_IRQ(n, priority),                       \
				uart_intel_lw_isr,                              \
				DEVICE_DT_INST_GET(n), 0);		        \
		                                                                \
		irq_enable(DT_INST_IRQN(n));                                    \
	}

#define UART_INTEL_LW_IRQ_CONFIG_INIT(n)                                        \
	.irq_config_func = uart_intel_lw_irq_config_func_##n,                   \
	.irq_num = DT_INST_IRQN(n),

#else

#define UART_INTEL_LW_IRQ_CONFIG_FUNC(n)
#define UART_INTEL_LW_IRQ_CONFIG_INIT(n)

#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

#define UART_INTEL_LW_DEVICE_INIT(n)                                        \
UART_INTEL_LW_IRQ_CONFIG_FUNC(n)                                            \
static struct uart_intel_lw_device_data uart_intel_lw_dev_data_##n = {      \
	.uart_cfg =                                                         \
	{                                                                   \
			.baudrate = DT_INST_PROP(n, current_speed),         \
			.parity = DT_INST_ENUM_IDX(n, parity),              \
			.stop_bits = DT_INST_ENUM_IDX(n, stop_bits),        \
			.data_bits = DT_INST_ENUM_IDX(n, data_bits),        \
			.flow_ctrl = DT_INST_PROP(n, hw_flow_control) ?     \
				UART_CFG_FLOW_CTRL_RTS_CTS :                \
				UART_CFG_FLOW_CTRL_NONE,                    \
	},                                                                  \
};                                                                          \
	                                                                    \
static const struct uart_intel_lw_device_config uart_intel_lw_dev_cfg_##n = {     \
	.base = DT_INST_REG_ADDR(n),                                              \
	.flags = ((DT_INST_PROP(n, fixed_baudrate)?INTEL_LW_UART_FB:0)            \
			  |(DT_INST_PROP(n, hw_flow_control)?INTEL_LW_UART_FC:0)),\
	UART_INTEL_LW_IRQ_CONFIG_INIT(n)                                          \
};                                                                                \
	                                                                          \
DEVICE_DT_INST_DEFINE(n,                                                          \
		      uart_intel_lw_init,                                         \
		      NULL,                                                       \
		      &uart_intel_lw_dev_data_##n,                                \
		      &uart_intel_lw_dev_cfg_##n,                                 \
		      PRE_KERNEL_1,                                               \
		      CONFIG_SERIAL_INIT_PRIORITY,                                \
		      &uart_intel_lw_driver_api);

DT_INST_FOREACH_STATUS_OKAY(UART_INTEL_LW_DEVICE_INIT)
